package com.atguigu.gmall.order.service;

import com.atguigu.gmall.cart.entity.CartItem;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.common.exception.GmallException;
import com.atguigu.gmall.oms.entity.OrderEntity;
import com.atguigu.gmall.order.feign.*;
import com.atguigu.gmall.order.interceptor.LoginInterceptor;
import com.atguigu.gmall.order.vo.OrderConfirmVo;
import com.atguigu.gmall.order.vo.OrderItemVo;
import com.atguigu.gmall.order.vo.OrderSubmitVo;
import com.atguigu.gmall.pms.api.GmallPmsApi;
import com.atguigu.gmall.pms.entity.SkuAttrValueEntity;
import com.atguigu.gmall.pms.entity.SkuEntity;
import com.atguigu.gmall.sms.vo.ItemSaleVo;
import com.atguigu.gmall.ums.entity.UserAddressEntity;
import com.atguigu.gmall.ums.entity.UserEntity;
import com.atguigu.gmall.wms.vo.SkuLockVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.gson.Gson;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Service
public class OrderService {
    @Autowired
    GmallPmsClient pmsClient;
    @Autowired
    GmallWmsClient wmsClient;
    @Autowired
    GmallSmsClient smsClient;
    @Autowired
    GmallOmsClient omsClient;
    @Autowired
    GmallUmsClient umsClient;
    @Autowired
    GmallCartClient cartClient;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    StringRedisTemplate redisTemplate;
    String orderTokenPrefixe = "order:token:";
    /*
        同步执行，性能差
        可以使用异步编排优化
     */
    //查询结算页需要的数据的业务
    public OrderConfirmVo confirm() {
        OrderConfirmVo confirmVo = new OrderConfirmVo();
        //1、从GMALL-TOKEN中获取userId： 使用LoginInterceptor处理传递参数
        Long userId = LoginInterceptor.getUserInfo().getUserId();
        //2、获取userId用户在redis中的购物车中的选中的购物项集合：cart服务
        //购物车操作的接口 有cart服务提供
        CompletableFuture<List<CartItem>> cartItemsCompletableFuture = CompletableFuture.supplyAsync(() -> {
            ResponseVo<List<CartItem>> checkedCartItemsByUserId = cartClient.queryCheckedCartItemsByUserId(userId);
            List<CartItem> cartItems = checkedCartItemsByUserId.getData();
            if (CollectionUtils.isEmpty(cartItems)) {
                throw new GmallException("没有选中的购物项");// 如果异常希望后端自己处理，一般自定义异常类(接收异常状态码、信息)
                //通过异常处理器来处理异常  @ControllerAdvice注解标注的类+@ExceptionHandler(exception=xxx.class) 参考上个项目
            }
            return cartItems;
        });
        CompletableFuture<Void> orderItemVosCompletableFuture = cartItemsCompletableFuture.thenAcceptAsync((cartItems) -> {
            //3、遍历所有的购物项：转为一个个的订单项(只使用购物项的skuId转订单项，订单项需要的其他数据从数据库查询)
            List<OrderItemVo> orderItemVos = cartItems.stream().map(cartItem -> {
                OrderItemVo orderItemVo = new OrderItemVo();//订单项除了 skuId和数量等数据  其他的都以数据库为准
                orderItemVo.setCount(cartItem.getCount());
                orderItemVo.setSkuId(cartItem.getSkuId());
                //3.1 查询sku
                CompletableFuture<Void> skuCompletableFuture = CompletableFuture.runAsync(() -> {
                    ResponseVo<SkuEntity> skuEntityResponseVo = pmsClient.querySkuById(Long.parseLong(cartItem.getSkuId()));
                    SkuEntity skuEntity = skuEntityResponseVo.getData();
                    if (skuEntity != null) {
                        orderItemVo.setPrice(skuEntity.getPrice());
                        orderItemVo.setTitle(skuEntity.getTitle());
                        orderItemVo.setDefaultImage(skuEntity.getDefaultImage());
                        orderItemVo.setWeight(new BigDecimal(skuEntity.getWeight()));
                    }
                });
                //3.2 查询 sku的销售属性
                CompletableFuture<Void> saleAttrValuesCompletableFuture = CompletableFuture.runAsync(() -> {
                    ResponseVo<List<SkuAttrValueEntity>> querySkuAttrValuesBySkuId = pmsClient.querySkuAttrValuesBySkuId(Long.parseLong(cartItem.getSkuId()));
                    List<SkuAttrValueEntity> skuAttrValueEntities = querySkuAttrValuesBySkuId.getData();
                    if (!CollectionUtils.isEmpty(skuAttrValueEntities)) {
                        orderItemVo.setSaleAttrs(skuAttrValueEntities);
                    }
                });
                //3.3 查询sku的营销属性
                CompletableFuture<Void> salesCompletableFuture = CompletableFuture.runAsync(() -> {
                    ResponseVo<List<ItemSaleVo>> queryItemSalesBySkuId = smsClient.queryItemSalesBySkuId(Long.parseLong(cartItem.getSkuId()));
                    List<ItemSaleVo> itemSaleVos = queryItemSalesBySkuId.getData();
                    if (!CollectionUtils.isEmpty(itemSaleVos)) {
                        orderItemVo.setSales(itemSaleVos);
                    }
                });
                //3.4 查询sku的库存
                CompletableFuture<Void> wareCompletableFuture = CompletableFuture.runAsync(() -> {
                    ResponseVo<Boolean> booleanResponseVo = wmsClient.queryItemSkuStoreBySkuId(Long.parseLong(cartItem.getSkuId()));
                    Boolean store = booleanResponseVo.getData();
                    if (store != null) {
                        orderItemVo.setStore(store);
                    }
                });
                CompletableFuture.allOf(skuCompletableFuture,saleAttrValuesCompletableFuture,
                        salesCompletableFuture,wareCompletableFuture).join();
                return orderItemVo;
            }).collect(Collectors.toList());
            confirmVo.setOrderItems(orderItemVos);
        });


        //4、查询userId的地址列表
        CompletableFuture<Void> addressCompletableFuture = CompletableFuture.runAsync(() -> {
            ResponseVo<List<UserAddressEntity>> queryAddressesByUserId = umsClient.queryAddressesByUserId(userId);
            List<UserAddressEntity> userAddressEntities = queryAddressesByUserId.getData();
            if (!CollectionUtils.isEmpty(userAddressEntities)) {
                confirmVo.setAddresses(userAddressEntities);
            }
        });
        //创建查询用户地址列表接口
        //5、查询userId的用户积分: 查询user对象
        CompletableFuture<Void> boundsCompletableFuture = CompletableFuture.runAsync(() -> {
            ResponseVo<UserEntity> userEntityResponseVo = umsClient.queryUserById(userId);
            if (userEntityResponseVo.getData() != null) {
                confirmVo.setBounds(userEntityResponseVo.getData().getIntegration());
            }
        });
        //6、生成ordertoken
        CompletableFuture<Void> tokenCompletableFuture = CompletableFuture.runAsync(() -> {
            String timeId = IdWorker.getTimeId();//以时间戳+随机10进制字符串生成的32位长度的字符串
            confirmVo.setOrderToken(timeId);
            //将orderToken存到redis中一份： 和session的防止表单重复提交的操作一样
            redisTemplate.opsForValue().set(orderTokenPrefixe+timeId , timeId);

        });
        //未实现：  优惠券、支付方式、配送方式、查询userId的发票列表
        CompletableFuture.allOf(cartItemsCompletableFuture,orderItemVosCompletableFuture,
                addressCompletableFuture,boundsCompletableFuture,tokenCompletableFuture).join();

        return confirmVo;
    }
    Gson gson = new Gson();
    /*
    结算页 提交订单  创建订单的业务方法
     */
    public OrderEntity createOrder(OrderSubmitVo submitVo) {
        //1、防重：  使用orderToken判断处理
        String submitOrderToken = submitVo.getOrderToken();
        //使用token到redis中查询缓存，如果有则删除 删除成功 代表第一次提交订单，删除失败或者没有缓存 代表第N次提交请求
        //查询+删除 判断  希望具有原子性     借助LUA
        //KEYS[1]  ordertokenprefixe+token   ARGV[1] token
        String script = "if redis.call('get' , KEYS[1])==ARGV[1] " +
                "then  return redis.call('del' , KEYS[1]) " +
                "else return 0 end";
        Boolean flag = redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),
                Arrays.asList(orderTokenPrefixe + submitOrderToken), submitOrderToken);
        if(!flag){//订单重复提交
            throw new GmallException("您的订单已经提交过，请勿重复提交");
        }
        //2、验价：  使用前端提交的 订单项 skuId查询所有的商品的价格 和 订单项数量一起计算 总价验价
        List<OrderItemVo> items = submitVo.getItems();
        BigDecimal totalPrice = submitVo.getTotalPrice();
        if(CollectionUtils.isEmpty(items)){
            //前端没有提交订单项
            throw new GmallException("没有选中要购买的商品！");
        }
        //实时总价格
        BigDecimal currentTotalPrice = items.stream().map(orderItemVo -> {
            String skuId = orderItemVo.getSkuId();
            //使用skuId到数据库查询商品价格
            ResponseVo<SkuEntity> skuEntityResponseVo = pmsClient.querySkuById(Long.parseLong(skuId));
            SkuEntity skuEntity = skuEntityResponseVo.getData();
            //使用查询到的商品的单价* 订单项该商品的数量
            return skuEntity.getPrice().multiply(new BigDecimal(orderItemVo.getCount()));
        }).reduce((a, b) -> a.add(b)).get();
        System.out.println("totalPrice: "+ totalPrice+" , currentTotalPrice: "+ currentTotalPrice);
        if(currentTotalPrice.compareTo(totalPrice)!=0){//前端提交的价格 和数据库实时价格不一致
            throw new GmallException("页面数据已过期，请刷新重试");
        }
        System.out.println("totalPrice: "+ totalPrice+" , currentTotalPrice: "+ currentTotalPrice);

        //3、验库存锁库存：  验证库存是否足够 如果够锁定库存(需要具有原子性： 分布式锁)
        /*
            一个订单有多个订单项：
                    每个订单项对应一个sku
                    一个sku有多个库存数据， sku锁定库存时只能锁定一个仓库
              将所有的订单项遍历转为 锁库存的sku对象
                - boolean stock;//是否锁定库存
                - Integer count;//购买数量
                - Long skuId;
                - Long wareSkuId;//锁定库存成功的 仓库id
                - String orderToken;//本次sku锁定库存对应的订单编号： 如果该订单编号订单撤销 可以释放锁定的库存
            远程访问wms：将要验库存的对象集合提交给wms服务，wms遍历所有的对象 验库存锁库存如果成功设置对象的stock为
            true，同时将锁定库存的仓库id 设置给wareSkuId。如果失败设置stock为false
                        执行结束后  将锁库存失败的对象集合返回，当前业务中接收返回的结果，如果有失败的对象集合可以交给
                        前端遍历展示给用户。 如果没有返回失败的集合 表示所有的订单项锁库存成功

         */
        List<SkuLockVo> skuLockVos = items.stream().map(orderItemVo -> {
            SkuLockVo skuLockVo = new SkuLockVo();
            skuLockVo.setSkuId(Long.parseLong(orderItemVo.getSkuId()));
            skuLockVo.setCount(orderItemVo.getCount());
            skuLockVo.setOrderToken(submitVo.getOrderToken());
            return skuLockVo;
        }).collect(Collectors.toList());
        ResponseVo<List<SkuLockVo>> checkAndLock = wmsClient.checkAndLock(skuLockVos);
        List<SkuLockVo> failSkuLockVos = checkAndLock.getData();
        if(!CollectionUtils.isEmpty(failSkuLockVos)){
            //代表有锁定库存失败的
            throw new GmallException("手慢了，库存不足 : " + gson.toJson(failSkuLockVos));
        }
        //上面 锁定库存时 有异常  业务代码中已经释放了库存
        //====当前行出现异常： 订单取消创建，之前锁定的库存如何释放
        //下面 保存订单时如果出现异常  订单数据保存失败，但是上面的库存锁定状态没有释放
        Long userId = LoginInterceptor.getUserInfo().getUserId();
        OrderEntity orderEntity = null;
        try {
            //4、创建订单：将结算页提交的数据转为订单数据持久化保存(oms: order:submitVo  order_item: item)
            // oms创建订单接口分析：传参：OrderSubmitVo+userId   响应：返回创建成功的订单对象
            ResponseVo<OrderEntity> orderEntityResponseVo = omsClient.saveOrder(userId, submitVo);
            orderEntity = orderEntityResponseVo.getData();//获取新增成功的订单对象
//        return orderEntity;
        } catch (Exception e) {
            e.printStackTrace();
            //订单创建失败：释放库存
            /*
                异步执行的任务的方式：
                1、子线程：
                        线程池、异步编排、springTask
                2、mq消息队列： 不需要关心异步执行的结果、并且不希望耦合去调用其他服务接口，方便以后的扩展 使用消息队列
                    异步解耦合
             */
            //TODO: 异步执行释放库存操作
            //向消息队列发送释放库存的消息  order服务生产者
            //参数1：交换机，参数2：路由key  参数3：消息内容
            rabbitTemplate.convertAndSend("order_exchange","stock.unlock",submitVo.getOrderToken());
            //wms服务创建消费者监听释放库存的消息:获取orderToken查找 redis中缓存的锁定库存的数据 处理业务解锁库存， 消费者

        }

        //TODO: 5、删除购物车：只删除选中的购物项: 异步执行
        //order服务订单创建成功后发送  删除购物项消息  生产者
        Map<String,Object> map = new HashMap();
        map.put("userId",userId);
        map.put("skuIds" , skuLockVos.stream().map(SkuLockVo::getSkuId).collect(Collectors.toList()) );
        rabbitTemplate.convertAndSend("order_exchange" ,"cart.delete.checked",map);

        //给cart服务传递  userId 和 skuIds 的数据，

        //cart服务：监听删除购物项的消息  执行删除业务   消费者
        // 查找购物车需要使用userId： ， 购物车中 skuId和CartItem形成映射 ，根据skuId可以确定购物车中的唯一的购物项
        return orderEntity;
    }
}
